# Import der Visualisierungsbibliothek und Pandas
import pandas as pd
import plotly_express as px
# Lies vorbereitete Daten aus Pickle-Datei ein
df = pd.read_pickle("kiva_data_after_processing.pkl")
Unser Businessmodell hängt davon ab, dass möglichst viele Projekte erfolgreich finanziert werden, damit weiterhin Darlehnsnehmende ihre Projekte einstellen und Investierende Geld zur Verfügung stellen.
Es kann nicht das Ziel sein, die Darlehnshöhe zu steigern, denn die Kredite müssen realistisch rückzahlbar sein.
Je höher die Finanzierungsrate eines Projekts, desto wahrscheinlicher ist es auch, dass die Investition erfolgreich ist und den geplanten Return on Investment abwirft.
Ich möchte daher zunächst die Finanzierungsraten genauer anschauen bzw diejenigen Projekte anschauen, die nicht vollständig finanziert wurden.
Ich betrachte zunächst die Anzahl und den Anteil unvollständig finanzierter Projekte, also diejenigen Projekte mit funding_ratio < 100%:
# Label für Kategorien festlegen
category = ["0%", "1-99%", "100%"]
# Wertebereiche für Kategorien (bins) festlegen
funding_ratio = [-0.1, 0, 99,100]
# Teile die Projekte in die drei oben festegelegten Kategorien ein
df["funding"] = pd.cut(x=df.loc[:,"funding_ratio"],
bins=funding_ratio,
labels=category
)
# Überprüfe, wie die Werte auf die Kategorien verteilt sind
df_cats = df.funding.value_counts()
df_cats
100% 598590 1-99% 44747 0% 3278 Name: funding, dtype: int64
# Lege Data Frame mit ausschließlich unvollständig finanzierten Projekten an
df_incomplete = df.loc[(df.loc[:,"funding_ratio"]<100),:]
# Ermittle Anzahl unvollständig finanzierter Projekte
number = df_incomplete.funding_ratio.value_counts().sum()
# Ermittle Anteil unvollständig finanzierter Projekte in Bezug auf die komplette Anzahl an Projekten
ratio = (df_incomplete.funding_ratio.value_counts().sum()/df.funding_ratio.value_counts().sum())*100
ratio = ratio.round(decimals=1)
# Gib Anzahl und Anteil aus
print(f"Anzahl unvollständig finanzierter Projekte: {number}")
print(f"Anteil unvollständig finanzierter Projekte: {ratio} %")
Anzahl unvollständig finanzierter Projekte: 48035 Anteil unvollständig finanzierter Projekte: 7.4 %
Im Folgenden betrachte ich nur die unvollständig finanzierten Projekte, die immerhin 7,4% aller Projekte ausmachen.
Für die erste, univariate Visualisierung verwende ich ein Histogramm, um die Anzahl unvollständig finanzierter Projekte bezöglich unterschiedlicher Finanzierungsraten zu analysieren.
# Erstelle Histogramm
fig = px.histogram(df_incomplete,
x = "funding_ratio",
labels = {"funding_ratio": "Finanzierungsrate in %"},
nbins = 25,
title = "Title",
hover_data = df_incomplete.columns)
# Lege die Farbe für die Bins im Histogramm fest
color_sequence = ["#faaf72"] * 25 # Default für alle Bins
color_sequence[0] = "#921212" # Hebe den ersten Bin hervor
# Update color_discrete_sequence Eigenschaft des Histogramms
fig.update_traces(marker=dict(color=color_sequence))
# Passe Layout der Grafik an
fig.update_layout(
width=1000,
height=620,
title={
'text': '<b>Anzahl unvollständig finanzierter Projekte nach Finanzierungsrate</b>',
'y':0.9,
'x':0.5,
'xanchor': 'center'
},
title_font_size=20,
yaxis_title="Anzahl",
bargap=0.1,
plot_bgcolor = "rgba(0, 0, 0, 0)",
paper_bgcolor = "rgba(0, 0, 0, 0)"
)
# Stelle sicher, dass die Bins bei 0 starten und bei 99 enden
fig.update_traces(xbins=dict(
start=0.0,
end=99.0
))
# Zeige horizontale Linie für den Durchschnittswert (Anzahl unvollständiger Projekte/Anzahl bins = 48035/20 = 2402)
fig.add_hline(y=2402,
line_width=3,
line_dash="dash",
line_color="grey"
)
# Füge eine Fußnote zur Erklärung der Linie hinzu
fig.add_annotation(
showarrow=False,
text="Durchschnitt",
font=dict(size=10),
x=99,
y=2500
)
# Plotte Graph
fig.show()
Das Histogramm zeigt auf der x-Achse die Finanzierungsrate in Prozent. Das entspricht dem erfolgreich finanzierten Anteil am gewünschten Gesamtdarlehn. Je höher der Prozentsatz, desto näher sind die Projektinitiator*innen an ihr Finanzierungsziel gekommen.
Auf der y-Achse ist die Anzahl der Projekte abgetragen.
Der Durchschnitt ist als Quotient von Gesamtanzahl und Anzahl der Balken im Histogramm eingezeichnet.
Es wäre zu erwarten, dass sich die unvollständig finanzierten Projekte relativ gleichmäßig auf die Finanzierungsraten aufteilen.
Besonders in den Randbereichen gibt es deutliche Abweichungen vom Erwartungsbild. Wir sehen einen großen Peak bei 0, dann einen starken Abfall in den kleinen Prozentbereichen. In der Mitte steigt die Kurve auf überdurchschnittliche Werte an um dann gegen 100% wieder stark abzufallen.
Fast doppelt so viele Projekte wie erwartet konnten gar keine Finanzierung erzielen (<2,5% Finanzierungsrate für 4450 Projekte). Am anderen Ende der Skala gibt es dagegen unterdurschnittlich viele Projekte mit Finanzierungsraten zwischen ~83% und 100%.
Dafür wäre eine mögiche Erklärung, dass wenn ein Projekt schon über 80% seiner Zielsumme erreicht hat, es sehr wahrscheinlich ist, dass das Projekt Erfolg hat, selbst wenn nicht mehr 100% des angestrebten Betrags erreicht werden. Diese Projekte sind für Investoren also sher attraktiv.
Die nächste Fragestellung lautet also, warum es so eine große Anzahl (fast) komplett unfinanzierter Projekte gibt.
Dafür will ich zunächst herausfinden, ob es in der globalen Verteilung der unfinanzierten Projekte Auffälligkeiten oder Muster gibt.
Zunächst aggregiere ich die unfinanzierten Projekte auf Länderebene. Neben den absoluten Zahlen ist auch der Prozentsatz je Land interessant.
# Lege Data Frame mit ausschließlich unfinanzierten Projekten an
df_zero = df.loc[(df.loc[:,"funding_ratio"]==0),:]
# Aggregiere unfinanzierte Projekte pro Land
df_zero_agg = df_zero.groupby(by="country", as_index=False).agg(func={"country_code":"count"})
# Aggregiere alle Projekte pro Land
df_agg = df.groupby(by="country", as_index=False).agg(func={"country_code":"count"})
# Kombiniere beide Dataframes, so dass beide Informationen je Land in einem df vorliegen
df_country = df_agg.merge(df_zero_agg, left_on="country", right_on="country", how='inner')
# Gib den Spalten aussagekräftigere Namen
df_country = df_country.rename(columns={"country_code_x": "projects_total", "country_code_y": "projects_unfunded"})
# Berechne Prozentsatz unfinanzierter Projekte
df_country["unfunded_projects_ratio"] = (df_country["projects_unfunded"] / df_country["projects_total"]) * 100
df_country["unfunded_projects_ratio"] = df_country.unfunded_projects_ratio.round(decimals=1)
df_country
| country | projects_total | projects_unfunded | unfunded_projects_ratio | |
|---|---|---|---|---|
| 0 | Afghanistan | 2 | 0 | 0.0 |
| 1 | Albania | 1934 | 12 | 0.6 |
| 2 | Armenia | 8629 | 22 | 0.3 |
| 3 | Azerbaijan | 1942 | 0 | 0.0 |
| 4 | Belize | 124 | 0 | 0.0 |
| ... | ... | ... | ... | ... |
| 82 | Vietnam | 9532 | 59 | 0.6 |
| 83 | Virgin Islands | 2 | 2 | 100.0 |
| 84 | Yemen | 2313 | 6 | 0.3 |
| 85 | Zambia | 775 | 0 | 0.0 |
| 86 | Zimbabwe | 3973 | 6 | 0.2 |
87 rows × 4 columns
Ich brauche noch eine Kennzahl, die die absolute und die relative Information verbindet.
Dafür dividiere ich die Anzahl der unfinanzierten Projekte durch die unfunded_projects_ratio und ziehe die Wurzel aus dem Ergebnis. Die Wurzel ziehe ich, damit die Spannweite der Werte kleiner wird.
# Berechne neue Kennzahl als "unfunded_projects_KPI"
df_country["unfunded_projects_KPI"] = (df_country["projects_unfunded"] * df_country["unfunded_projects_ratio"])**(1/2)
df_country["unfunded_projects_KPI"] = df_country["unfunded_projects_KPI"].astype("int64")
df_country.sort_values(by="unfunded_projects_KPI",ascending=False).head(10)
| country | projects_total | projects_unfunded | unfunded_projects_ratio | unfunded_projects_KPI | |
|---|---|---|---|---|---|
| 80 | United States | 6092 | 837 | 13.7 | 107 |
| 34 | Kenya | 73525 | 526 | 0.7 | 19 |
| 54 | Pakistan | 26453 | 242 | 0.9 | 14 |
| 83 | Virgin Islands | 2 | 2 | 100.0 | 14 |
| 22 | El Salvador | 39845 | 223 | 0.6 | 11 |
| 49 | Myanmar (Burma) | 1870 | 46 | 2.5 | 10 |
| 53 | Nigeria | 5696 | 82 | 1.4 | 10 |
| 15 | Colombia | 21961 | 156 | 0.7 | 10 |
| 11 | Cambodia | 26957 | 170 | 0.6 | 10 |
| 76 | Togo | 5747 | 71 | 1.2 | 9 |
# Erstelle Choropleth Map
fig = px.choropleth(data_frame=df_country,
locations='country',
locationmode='country names',
color='unfunded_projects_KPI',
color_continuous_scale= "OrRd",
labels={"projects_unfunded": "Anzahl",
"unfunded_projects_ratio": "Anteil in %",
"unfunded_projects_KPI": "Kennzahl"},
hover_name="country",
hover_data={"country":False,"projects_unfunded":True, "unfunded_projects_ratio": True}
)
# Zeige Kontur um die Länder
fig.update_traces(marker_line_width=0.5)
# Update Layout
fig.update_layout(geo={"resolution":50,
"showframe":False,
"showcoastlines":False,
"projection_type":"equirectangular"
},
title={
'text': '<b>Unfinanzierte Projekte nach Ländern</b>',
'x':0.5,
'xanchor': 'center',
'yanchor': 'top'
},
title_font_size=20
)
# Plotte Graph
fig.show()
Für jedes Land, in dem es Crowd-Investment-Projekte gibt, zeigt die Farbe des Landes an, wie viele Projekte mit Finanzierungsrate 0% es in dem jeweiligen Land gibt, und zwar nicht als absolute Anzahl, sondern als Kombination von absoluten und relativem Anteil.
Je dunkler die Farbe, desto mehr unfinanzierte Projekte gibt es.
Es wäre zu erwarten, dass sich die unfinanzierten Projekte relativ gleichmäßig auf alle Länder verteilen.
Die USA sind als einziges Land dunkelrot markiert, die anderen Länder heben sich kaum von der beigen Farbe ab, die die Null markiert.
Die USA stechen als einziges Land deutlich aus allen anderen hervor.
Daher möchte ich als nächtes untersuchen, was die unfinanzierten Projekte in den USA von den anderen Projekten unterscheidet.
Ich teile alle Projekte in vier Kategorien ein:
Als geringe Finanzierungsrate betrachte ich in diesem Zusammenhang Werte unter 20%, als hoch alles über 80%.
Für jede der Kategorien will ich die durchschnittliche angestrebte Darlehnssumme je Themenbereich aufzeigen.
# Erstelle neuen Dataframe mit neuer Spalte, die die Kategorisierung enthält
df_usa = df
df_usa.loc[(df_usa.loc[:,"country"]=="United States") & (df_usa.loc[:,"funding_ratio"]<20),"funding_cat"] = "USA unfunded"
df_usa.loc[(df_usa.loc[:,"country"]=="United States") & (df_usa.loc[:,"funding_ratio"]>80),"funding_cat"] = "USA funded"
df_usa.loc[(df_usa.loc[:,"country"]!="United States") & (df_usa.loc[:,"funding_ratio"]<20),"funding_cat"] = "World unfunded"
df_usa.loc[(df_usa.loc[:,"country"]!="United States") & (df_usa.loc[:,"funding_ratio"]>80),"funding_cat"] = "World funded"
df_usa
| funded_amount | loan_amount | activity | sector | country_code | country | currency | term_in_months | lender_count | repayment_interval | borrowers_female | borrowers_male | borrower_count | funding_ratio | avg_investment | funding | funding_cat | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 300 | 300 | Fruits & Vegetables | Food | PK | Pakistan | PKR | 12 | 12 | irregular | 1 | 0 | 1 | 100.0 | 25.00000 | 100% | World funded |
| 1 | 575 | 575 | Rickshaw | Transportation | PK | Pakistan | PKR | 11 | 14 | irregular | 2 | 0 | 2 | 100.0 | 41.07143 | 100% | World funded |
| 2 | 150 | 150 | Transportation | Transportation | IN | India | INR | 43 | 6 | bullet | 1 | 0 | 1 | 100.0 | 25.00000 | 100% | World funded |
| 3 | 200 | 200 | Embroidery | Arts | PK | Pakistan | PKR | 11 | 8 | irregular | 1 | 0 | 1 | 100.0 | 25.00000 | 100% | World funded |
| 4 | 400 | 400 | Milk Sales | Food | PK | Pakistan | PKR | 14 | 16 | monthly | 1 | 0 | 1 | 100.0 | 25.00000 | 100% | World funded |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 671179 | 0 | 25 | Livestock | Agriculture | PY | Paraguay | USD | 13 | 0 | monthly | 1 | 0 | 1 | 0.0 | 0.00000 | 0% | World unfunded |
| 671181 | 0 | 25 | Livestock | Agriculture | PK | Pakistan | PKR | 13 | 0 | monthly | 1 | 0 | 1 | 0.0 | 0.00000 | 0% | World unfunded |
| 671182 | 0 | 125 | Livestock | Agriculture | MX | Mexico | MXN | 13 | 0 | monthly | 2 | 0 | 2 | 0.0 | 0.00000 | 0% | World unfunded |
| 671184 | 0 | 875 | Livestock | Agriculture | BO | Bolivia | BOB | 13 | 0 | monthly | 2 | 0 | 2 | 0.0 | 0.00000 | 0% | World unfunded |
| 671188 | 0 | 250 | Livestock | Agriculture | GH | Ghana | GHS | 13 | 0 | monthly | 1 | 0 | 1 | 0.0 | 0.00000 | 0% | World unfunded |
646617 rows × 17 columns
# Erstelle Dataframe, wo die Anzahl der Projekte je Finanzierungskategorie und Themenbereich aggregiert wird
df_usa_agg = df_usa.groupby(by=["funding_cat","sector"], as_index=False).agg(func={"loan_amount":"mean"})
df_usa_agg
| funding_cat | sector | loan_amount | |
|---|---|---|---|
| 0 | USA funded | Agriculture | 7437.425595 |
| 1 | USA funded | Arts | 5081.658291 |
| 2 | USA funded | Clothing | 4821.958175 |
| 3 | USA funded | Construction | 4992.796610 |
| 4 | USA funded | Education | 5267.796610 |
| 5 | USA funded | Entertainment | 4335.000000 |
| 6 | USA funded | Food | 6184.717608 |
| 7 | USA funded | Health | 5255.357143 |
| 8 | USA funded | Housing | 5791.000000 |
| 9 | USA funded | Manufacturing | 5016.379310 |
| 10 | USA funded | Personal Use | 6236.111111 |
| 11 | USA funded | Retail | 4772.098214 |
| 12 | USA funded | Services | 4812.901182 |
| 13 | USA funded | Transportation | 4861.111111 |
| 14 | USA funded | Wholesale | 4760.937500 |
| 15 | USA unfunded | Agriculture | 7126.442308 |
| 16 | USA unfunded | Arts | 3510.156250 |
| 17 | USA unfunded | Clothing | 4120.905172 |
| 18 | USA unfunded | Construction | 3848.437500 |
| 19 | USA unfunded | Education | 3882.843137 |
| 20 | USA unfunded | Entertainment | 4043.072289 |
| 21 | USA unfunded | Food | 5090.959821 |
| 22 | USA unfunded | Health | 4571.428571 |
| 23 | USA unfunded | Housing | 3323.214286 |
| 24 | USA unfunded | Manufacturing | 6166.666667 |
| 25 | USA unfunded | Personal Use | 6825.000000 |
| 26 | USA unfunded | Retail | 4453.808594 |
| 27 | USA unfunded | Services | 4115.503247 |
| 28 | USA unfunded | Transportation | 3559.210526 |
| 29 | World funded | Agriculture | 765.534036 |
| 30 | World funded | Arts | 923.287286 |
| 31 | World funded | Clothing | 1076.003810 |
| 32 | World funded | Construction | 989.468570 |
| 33 | World funded | Education | 975.069963 |
| 34 | World funded | Entertainment | 939.162754 |
| 35 | World funded | Food | 827.496484 |
| 36 | World funded | Health | 995.570395 |
| 37 | World funded | Housing | 690.034044 |
| 38 | World funded | Manufacturing | 843.266721 |
| 39 | World funded | Personal Use | 486.760125 |
| 40 | World funded | Retail | 734.362057 |
| 41 | World funded | Services | 876.714104 |
| 42 | World funded | Transportation | 631.519291 |
| 43 | World funded | Wholesale | 1360.838870 |
| 44 | World unfunded | Agriculture | 1125.655022 |
| 45 | World unfunded | Arts | 1037.083333 |
| 46 | World unfunded | Clothing | 1265.844298 |
| 47 | World unfunded | Construction | 1671.052632 |
| 48 | World unfunded | Education | 1736.466165 |
| 49 | World unfunded | Entertainment | 1581.250000 |
| 50 | World unfunded | Food | 1137.866242 |
| 51 | World unfunded | Health | 1612.656250 |
| 52 | World unfunded | Housing | 1096.893204 |
| 53 | World unfunded | Manufacturing | 1493.750000 |
| 54 | World unfunded | Personal Use | 855.503731 |
| 55 | World unfunded | Retail | 1058.257987 |
| 56 | World unfunded | Services | 1080.022659 |
| 57 | World unfunded | Transportation | 1311.228070 |
| 58 | World unfunded | Wholesale | 26162.500000 |
# Erstelle Scatterplot
fig = px.scatter(df_usa_agg,
x=["funding_cat"],
y='sector',
size='loan_amount',
color='loan_amount',
color_continuous_scale= "OrRd",
labels={"sector": "Sektor", "loan_amount": "\u2300 Darlehnssumme", "value": ""},
hover_name="sector"
)
# Lege Farbe für Extremwert fest, damit die anderen Werte auf der Farbskala nicht untergehen
excluded_index = 58 # Index des Extremwerts (siehe df_usa_agg: World unfunded, Wholesale)
excluded_color_value = 0 # Weise dem Extremwert einen Farbwert zu (d.h., die Farbe, die dem Wert 0 entspricht)
# Update Farbe für Extremwert
fig.data[0].marker.color[excluded_index] = excluded_color_value
# Zeige Umriss für die Bubbles
fig.update_traces(marker=dict(line=dict(width=0.5, color='DarkSlateGrey')),
selector=dict(mode='markers')
)
# Update Graph Layout
fig.update_layout(width=700,
title={'text': '<b>Finanzierte vs. unfinanzierte Projekte in den USA vs. World</b>',
'x':0.5,
'xanchor': 'center',
'yanchor': 'top',
},
title_font_size = 20,
plot_bgcolor = "rgba(0, 0, 0, 0)"
)
# Update Grid, damit es sichtbar ist
fig.update_yaxes(showline=True, linecolor='white', gridwidth=0.2, gridcolor='LightGrey')
# Plot anzeigen
fig.show()
Für jede Kategorie auf der x-Achse und jeden Themenbereich auf der y-Achse zeigt die Farbe und die Größe des Bubbles an, wie hoch die durchschnittliche Darlehnssumme ist.
Je dunkler die Farbe und größer die Blase, desto höher ist die durchschnittliche angezielte Darlehnssumme.
Es wäre zu erwarten, dass sich die unfinanzierten Projekte von den finanzierten Projekte abheben.
Die Unterschiede zwischen den USA und dem Rest der Länder sind erheblich, in den USA ist die durchschnittliche Darlehnssumme über alle Sektoren deutlich größer als bei den anderen Ländern (von einem Extremwert im Bereich "Wholesale" abgesehen).
Die Bubbles der unfinanzierten Projekte im Rest der Welt sind über alle Sektoren hinweg etwas größer als die der finanzierten Projekte.
Zwischen den unfinanzierten und den finanzierten Projekten innerhalb der USA gibt es diesen Unterschied allerdings nicht. Im Gegenteil, dort sind die Bubbles der unfinanzierten Projekte tendentiell kleiner als die der finanzierten.
Die Ausgangsfrage, was die unfinanzierten Projekte von den finanzierten Projekten abhebt, konnte bisher nicht abschließend geklärt werden. Klar ist jedoch, dass sich diese Projekte überwiegend und überproportional auf die USA konzentrieren.
Die USA sind die einzige vertretene Industrienation.
Ich halte es aus den folgenden Gründen für möglich, dass die Mehrheit der Investierenden es vorzieht, in einem Entwicklungs- oder Schwellenland zu investieren:
Erstens erscheinen dort die Lebensumstände insgesamt schwieriger und Investoren wollen einen Beitrag leisten, die Situation für Kleinunternehmer zu verbessern. Zweitens sind diese Staaten nicht in der Lage, ihre Bevölkerung zu befähigen, für ihren Lebensunterhalt zu sorgen. Drittens können auf Grund der geringeren Lebenshaltungskosten und der oft schwachen Währungen schon relativ geringe Dollarbeträge viel bewegen.
Im nächsten Schritt würde ich versuchen, mehr Daten über die Investierenden zu erhalten, zum Beispiel aus welchen Ländern Geld in welche Länder fließt.